﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Columbus.Tests.Features.Pages;
using Columbus.Tests.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Columbus.Tests.Features
{
    // ReSharper disable InconsistentNaming
    // BDD style naming
    public class NavigatorFixture
    {
        public class FixtureViewModel : ViewModel
        {

        }

        public class GivenInitializedNavigator
        {
            [TestClass]
            public class WhenNavigatingToView
            {
                private INavigator navigator;
                private readonly Func<string, FrameworkElement> viewLocator = s =>
                {
                    Assert.AreEqual(viewName, s);
                    return new EmptyPage();
                };
                private const string viewName = "view1";

                [TestInitialize]
                public void SetUp()
                {
                    Background.Execute = action => action();
                    var application = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithViewLocator(viewLocator);
                    application.StartNormally();
                    navigator = application.GetNavigator();
                    navigator.NavigateToView(viewName, new FixtureViewModel());
                }

                [TestMethod]
                public void IfViewNameIsNullThenThrowsException()
                {
                    AssertExtensions.Throws<ArgumentNullException>(() => new Navigator(n => null, n => null, null).NavigateToView(null));
                }

                [TestMethod]
                public void ThenViewLoaded()
                {
                    var view = navigator.NavigationFrame.Content as EmptyPage;
                    Assert.IsNotNull(view);
                    // ReSharper disable PossibleNullReferenceException
                    Assert.AreEqual(viewName, view.Name);
                    // ReSharper restore PossibleNullReferenceException
                }

                [TestMethod]
                public void ThenViewModelAttached()
                {
                    var view = navigator.NavigationFrame.Content as EmptyPage;
                    Assert.IsNotNull(view);
                    // ReSharper disable PossibleNullReferenceException
                    Assert.IsInstanceOfType(view.DataContext, typeof (FixtureViewModel));
                    // ReSharper restore PossibleNullReferenceException
                }

                [TestCleanup]
                public void CleanUp()
                {
                    Background.ResetToDefaults();
                }
            }

            [TestClass]
            public class WhenNavigatingBack
            {
                private INavigator navigator;
                private readonly Func<string, FrameworkElement> viewLocator = s =>
                {
                    Assert.AreEqual(viewName, s);
                    return new EmptyPage();
                };

                private const string viewName = "View1";
                private const string controllerName = "FixtureController";
                private FixtureController controller;

                [TestInitialize]
                public void SetUp()
                {
                    controller = new FixtureController();
                    Background.Execute = action => action();
                    var application = new Navigator(s =>
                    {
                        Assert.AreEqual(controllerName, s);
                        return controller;
                    }, viewLocator).CreateNewAppWithOneNavigator();
                    application.StartNormally();
                    navigator = application.GetNavigator();
                }

                [TestMethod]
                public void IfThereIsNoHistoryThenDoesNotNavigate()
                {
                    Assert.IsFalse(navigator.NavigateBack());
                }

                [TestMethod]
                public void IfThereNotEnoughHistoryThenDoesNotNavigate()
                {
                    navigator.NavigateTo<FixtureController>(c => c.DoNothing());
                    Assert.IsFalse(navigator.NavigateBack());
                }

                [TestMethod]
                public void ThenNavigatesBack()
                {
                    navigator.NavigateTo<FixtureController>(c => c.View1());
                    navigator.NavigateTo<FixtureController>(c => c.DoNothing());

                    Assert.IsInstanceOfType(navigator.NavigationFrame.Content, typeof (EmptyPage));
                }

                [TestCleanup]
                public void CleanUp()
                {
                    Background.ResetToDefaults();
                }
            }

            [TestClass]
            public class WhenNavigatingByRequest
            {
                private INavigator navigator;
                private readonly Func<string, FrameworkElement> viewLocator = s =>
                {
                    Assert.AreEqual(viewName, s);
                    return new EmptyPage();
                };

                private const string viewName = "view1";
                private const string controllerName = "FixtureController";
                private FixtureController controller;

                [TestInitialize]
                public void SetUp()
                {
                    controller = new FixtureController();
                    Background.Execute = action => action();
                    var application = new Navigator(s =>
                    {
                        Assert.AreEqual(controllerName, s);
                        return controller;
                    }, viewLocator).CreateNewAppWithOneNavigator();
                    application.StartNormally();
                    navigator = application.GetNavigator();
                }


                [TestMethod]
                public void IfRequestIsNullThenThrowsException()
                {
                    AssertExtensions.Throws<ArgumentNullException>(() => new Navigator(n => null, n => null, null).Navigate(null));
                }

                [TestMethod]
                public void ThenControllerParemterlessActionInvoked()
                {
                    navigator.Navigate(new NavigationRequest(navigator.NavigationFrameName, controllerName, "DoNothing"));

                    Assert.IsTrue(controller.IsParameterlessActionInvoked);
                }

                [TestMethod]
                public void ThenNavigationProgressDispatched()
                {
                    var wasInProgress = false;
                    var changedBackToIdle = false;
                    RegisterForNotification("IsNavigationInProgress", navigator.NavigationFrame, (x, y) =>
                    {
                        if ((bool)y.NewValue)
                            wasInProgress = true;
                        if (!(bool)y.NewValue && wasInProgress)
                            changedBackToIdle = true;
                    });
                    navigator.Navigate(new NavigationRequest(navigator.NavigationFrameName, controllerName, "DoNothing"));

                    Assert.IsTrue(wasInProgress);
                    Assert.IsTrue(changedBackToIdle);
                }

                [TestMethod]
                public void ThenControllerActionWithParametersInvoked()
                {
                    navigator.Navigate(new NavigationRequest(navigator.NavigationFrameName, controllerName, "DoNothing", new NavigationParameter(1)));

                    Assert.IsTrue(controller.IsActionWithParameterInvoked);
                }

                [TestMethod]
                public void IfActionNotFoundByNameThrowsException()
                {
                    AssertExtensions.Throws<Exception>(() => navigator.Navigate(new NavigationRequest(navigator.NavigationFrameName, controllerName, "NonExistentAction")));
                }

                [TestMethod]
                public void IfActionParametersNotMatchThrowsException()
                {
                    AssertExtensions.Throws<Exception>(() => navigator.Navigate(new NavigationRequest(navigator.NavigationFrameName, controllerName, "DoNothing", new NavigationParameter("x"))));
                }

                [TestCleanup]
                public void CleanUp()
                {
                    Background.ResetToDefaults();
                    Dispatcher.ResetToDefaults();
                }

                private static void RegisterForNotification(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
                {

                    var b = new Binding(propertyName) { Source = element };
                    var prop = DependencyProperty.RegisterAttached(
                        "ListenAttached" + propertyName,
                        typeof(object),
                        typeof(UserControl),
                        new PropertyMetadata(callback));

                    element.SetBinding(prop, b);
                }
            }

            public class FixtureController : Controller
            {
                public bool IsParameterlessActionInvoked { get; private set; }
                public bool IsActionWithParameterInvoked { get; private set; }

                public ActionResult DoNothing()
                {
                    IsParameterlessActionInvoked = true;
                    return Nothing();
                }

                public ActionResult DoNothing(int x)
                {
                    IsActionWithParameterInvoked = true;
                    return Nothing();
                }

                public ActionResult View1()
                {
                    return Page();
                }
            }
        }
    }
    // ReSharper restore InconsistentNaming
}
